import {
    BuildingBuildRequest,
    BuildingBuildResult,
    BuildingCheckRemoveRequest,
    BuildingCheckRemoveResult,
    BuildingCheckUpgradeRequest,
    BuildingCheckUpgradeResult,
    BuildingItem,
    BuildingMoveRequest,
    BuildingMoveResult,
    BuildingRemoveRequest,
    BuildingRemoveResult,
    BuildingUpgradeRequest,
    BuildingUpgradeResult
} from "db://assets/proto/new-world-api/v1/building";
import {GameEvent} from "db://assets/script/game-event";
import {director} from "cc";
import {http} from "db://assets/script/common/HTTP";
import {CityBuildingPreRemove} from "db://assets/script/common/events/city-events/city-building-pre-remove";
import {CityBuildingPrePut} from "db://assets/script/common/events/city-events/city-building-pre-put";
import {CityBuildingCheckUpgrade} from "db://assets/script/common/events/city-events/city-building-check-upgrade";
import {CityBuildingCheckRemove} from "db://assets/script/common/events/city-events/city-building-check-remove";
import {v4 as uuidv4} from 'uuid';
import {
    BuildingBuildReply,
    BuildingMoveReply,
    BuildingRemoveCheckReply,
    BuildingRemoveReply,
    BuildingUpgradeCheckReply,
    BuildingUpgradeReply
} from "db://assets/proto/new-world-ws/v1/internal";
import {lobby} from "db://assets/script/lobby";
import {CityBuildingRemove} from "db://assets/script/common/events/city-events/city-building-remove";
import {CityBuildingPut} from "db://assets/script/common/events/city-events/city-building-put";
import {BuildingStatus} from "db://assets/proto/shared/v1/wuni";
import {CityDateChange} from "db://assets/script/common/events/city-events/city-date-change";
import {CityBuildingPreUpgrade} from "./common/events/city-events/city-building-pre-upgrade";
import {CityBuildingUpgrade} from "db://assets/script/common/events/city-events/city-building-upgrade";
import {CityBuildingMovePut} from "db://assets/script/common/events/city-events/city-building-move-put";
import Long from "long";
import {cmm} from "db://assets/script/utils/cmm";
import {AssetCoreChange} from "db://assets/script/common/events/asset-events/asset-core-change";
import {configsHelper} from "db://assets/script/configs-helper";
import {serverTimeHelper} from "db://assets/script/server-time-helper";
import {cityUtils} from "db://assets/script/utils/city-utils";
import calcTilePositionByCellId = cityUtils.calcTilePositionByCellId;
import {BuildingConfigs} from "db://assets/proto/shared/v1/configs";
import {userInfo} from "db://assets/script/user-info";
import {MixPipesResult_MixInfo} from "db://assets/proto/new-world-api/v1/mix";
import {propsConfigsLoader} from "db://assets/script/configs/props-configs-loader";
import {MixingReceive} from "db://assets/script/common/events/mix-events/mixing-receive";


export class UserCityInfo {
    private userBuildings: Map<number, BuildingItem[]> = new Map();
    private unlockCellIds: bigint[] = []

    private _actionCallback: Map<string, (body: Uint8Array) => void> = new Map();

    inEdit = false;

    start() {
        this.onActionEvent()
        this.onDataChangeEvent()
        this.onRequestEvent()
    }

    // action为操作类的请求，请求仅发起，返回值仅有基础的资产校验相关，具体操作结果通过ws返回
    // action不会变更本地数据，数据变更需要等到ws返回后进行
    onActionEvent() {
        director.on(GameEvent.cityBuildingPrePut, this._actionBuild, this)
        director.on(GameEvent.cityBuildingPreMovePut, this._actionMove, this)
        director.on(GameEvent.cityBuildingPreRemove, this._actionRemove, this)
        director.on(GameEvent.cityBuildingPreUpgrade, this._actionUpgrade, this)
        director.on(GameEvent.cityBuildingCheckRemove, this._actionCheckRemove, this)
        director.on(GameEvent.cityBuildingCheckUpgrade, this._actionCheckUpgrade, this)
    }

    onRequestEvent() {
    }

    onDataChangeEvent() {
        director.on(GameEvent.cityDataChange, this._dataChangeEvent, this)
    }

    setBuildings(mapId: number, building: BuildingItem[], unlockCellIds: Long[]) {
        this.userBuildings.set(mapId, building);
        this.unlockCellIds = cmm.map(unlockCellIds, (item) => item.toBigInt());
    }

    checkInEdit(): boolean {
        return this.inEdit
    }

    checkUnlockCell(cellId: bigint, bc: BuildingConfigs) {
        if (bc.height * bc.width == 1) {
            return this.unlockCellIds.indexOf(cellId) != -1
        }
        const ps = calcTilePositionByCellId(cellId);
        const cellIds = cityUtils.calcBuildingOccupiedCellIds(ps.x, ps.y, bc.buildingId)
        for (let cid of cellIds) {
            if (this.unlockCellIds.indexOf(cid) != -1) {
                return true
            }
        }
        return false
    }

    checkOneCellUnlockCell(cellId: bigint) {
        return this.unlockCellIds.indexOf(cellId) != -1
    }

    changeInEdit() {
        this.inEdit = !this.inEdit;
    }

    loadBuildings(mapId: number): BuildingItem[] {
        const bs = this.userBuildings.get(mapId)
        return bs ? bs : [];
    }

    loadBuildingsNum(mapId: number, buildingId: Long): number {
        const bs = this.loadBuildings(mapId)
        return bs.filter(it => it.buildingId.eq(buildingId)).length
    }

    _dataChangeEvent(event: CityDateChange) {
        if (this._actionCallback.has(event.reqId)) {
            const callback = this._actionCallback.get(event.reqId)!
            callback(event.body)
            this._actionCallback.delete(event.reqId)
        }
        director.emit(GameEvent.cityWorkerNumChange, 1)
    }


    _actionBuild(event: CityBuildingPrePut) {
        const building = event.building
        const reqId = uuidv4()
        const param = BuildingBuildRequest.create({
            mapId: 1,
            x: building.tiledX,
            y: building.tiledY,
            buildingId: building.buildingId,
            direction: 1,
            reqId: reqId
        })
        this._actionCallback.set(reqId, (bs) => {
            const body = BuildingBuildReply.decode(bs);
            if (body.result != 0) lobby.instance.showToast(body.reason)
            else {
                const bs = this.userBuildings.get(1)!
                const buildingItem = BuildingItem.create({
                    buildingId: body.buildingId,
                    buildingUniqueId: body.buildingUniqueId,
                    x: body.x,
                    y: body.y,
                    completeAtMs: body.completeAtMs,
                    level: 0,
                    direction: body.direction,
                    status: BuildingStatus.Upgrading
                })
                bs.push(buildingItem)
                const pc = propsConfigsLoader.findPropConfigs(building.buildingConfig!.propId.toNumber())
                lobby.instance.showToast(`开始建造「${pc!.name}」！`)
                director.emit(GameEvent.assetCoreChange, new AssetCoreChange(event.costAssets));
                director.emit(GameEvent.cityBuildingPut, new CityBuildingPut(buildingItem))
                director.emit(GameEvent.uiUpdateCity, body.buildingUniqueId)

            }
        })
        http.requestAsync('POST', 'v1/building/build', BuildingBuildResult, BuildingBuildRequest.encode(param).finish())
            .catch(err => {
                this._actionCallback.delete(reqId)
                console.log(err)
            })
    }

    _actionRemove(event: CityBuildingPreRemove) {
        const building = event.buildingItem
        const reqId = uuidv4()
        const param = BuildingRemoveRequest.create({
            mapId: 1,
            buid: building.buildingUniqueId,
            reqId: reqId
        })
        this._actionCallback.set(reqId, (bs) => {
            const body = BuildingRemoveReply.decode(bs);
            if (body.result != 0) lobby.instance.showToast(body.reason)
            else {
                const bs = this.userBuildings.get(1)!
                const idx = bs.indexOf(event.buildingItem)
                if (idx == -1) return
                const b = bs[idx]
                b.completeAtMs = body.completeAtMs
                b.status = BuildingStatus.REMOVING
                director.emit(GameEvent.cityBuildingRemove, new CityBuildingRemove(body.buildingUniqueId, building.x, building.y, body.completeAtMs))
                director.emit(GameEvent.uiUpdateCity, b.buildingUniqueId)
            }
        })
        http.requestAsync('POST', 'v1/building/remove', BuildingRemoveResult, BuildingRemoveRequest.encode(param).finish())
            .catch(err => {
                this._actionCallback.delete(reqId)
                console.log(err)
            })
    }

    _actionCheckUpgrade(event: CityBuildingCheckUpgrade) {
        const reqId = uuidv4()
        const param = BuildingCheckUpgradeRequest.create({
            mapId: 1,
            buid: event.buildingItem.buildingUniqueId,
            reqId: reqId
        })
        this._actionCallback.set(reqId, (bs) => {
            const body = BuildingUpgradeCheckReply.decode(bs);
            if (body.result != 0) lobby.instance.showToast(body.reason)
            else {
                const bs = this.userBuildings.get(1)!
                const idx = bs.indexOf(event.buildingItem)
                if (idx == -1) return
                const b = bs[idx]
                b.level += 1
                b.status = BuildingStatus.NORMAL
                const bc = configsHelper.loadBuildingUpgradeConfig(b.buildingId, b.level)
                const bac = configsHelper.loadBuildingMixingFormulaConfigs(b.buildingId, b.level)
                if (b.level == 1) {
                    const ppc = configsHelper.loadBuildingMixingPipeConfigs(b.buildingId, b.level)
                    const nowMs = serverTimeHelper.loadServerTime()
                    if (bac && bac.input.length == 0) userInfo.mixInfo.setMixInfo('pipe_tag:building:1:' + b.buildingId.toString(), b.buildingUniqueId, MixPipesResult_MixInfo.create({
                        num: ppc!.maxProduce,
                        received: 0,
                        completeAtMs: bac.costMs.mul(ppc!.maxProduce).add(nowMs),
                        beginAtMs: Long.fromNumber(nowMs),
                        beginAtMsOffset: 0,
                    }))
                } else {
                    if (bac) director.emit(GameEvent.mixingTryReceive, new MixingReceive('pipe_tag:building:1:' + event.buildingItem.buildingId.toString(), event.buildingItem!.buildingUniqueId, b.level))
                }
                if (bc) {
                    const upgradeRewards = cmm.array2Map(bc.rewards, (it) => {
                        return {
                            k: it.propId.toNumber(),
                            v: it.num.toNumber()
                        }
                    })
                    lobby.instance.showAssetGainByMapToast(upgradeRewards)
                    const gainAssets: { [key: number]: Long } = {}
                    for (let reward of bc.rewards) {
                        gainAssets[reward.propId.toNumber()] = reward.num
                    }
                    director.emit(GameEvent.assetCoreChange, new AssetCoreChange(gainAssets));
                }
                director.emit(GameEvent.cityBuildingUpgradeComplete, b)
                director.emit(GameEvent.uiUpdateCity, b.buildingUniqueId)
            }
        })
        http.requestAsync('POST', 'v1/building/check/upgrade', BuildingCheckUpgradeResult, BuildingCheckUpgradeRequest.encode(param).finish())
            .catch(err => {
                console.log(err)
                this._actionCallback.delete(reqId)
            })
    }

    _actionUpgrade(event: CityBuildingPreUpgrade) {
        const reqId = uuidv4()
        const param = BuildingUpgradeRequest.create({
            mapId: 1,
            buid: event.buildingItem.buildingUniqueId,
            reqId: reqId
        })
        this._actionCallback.set(reqId, (bs) => {
            const body = BuildingUpgradeReply.decode(bs);
            if (body.result != 0) lobby.instance.showToast(body.reason)
            else {
                const bs = this.userBuildings.get(1)!
                const idx = bs.indexOf(event.buildingItem)
                if (idx == -1) return
                const b = bs[idx]
                b.completeAtMs = body.completeAtMs
                b.status = BuildingStatus.Upgrading
                const pc = propsConfigsLoader.findPropConfigs(configsHelper.loadBuildingConfig(event.buildingItem.buildingId)!.propId.toNumber())
                lobby.instance.showToast(`开始升级「${pc!.name}」！`)
                director.emit(GameEvent.assetCoreChange, new AssetCoreChange(event.costAssets));
                director.emit(GameEvent.cityBuildingUpgrade, new CityBuildingUpgrade(body.buildingUniqueId, event.buildingItem.x, event.buildingItem.y, body.completeAtMs))
                director.emit(GameEvent.uiUpdateCity, b.buildingUniqueId)
            }
        })
        http.requestAsync('POST', 'v1/building/upgrade', BuildingUpgradeResult, BuildingUpgradeRequest.encode(param).finish())
            .catch(err => {
                console.log(err)
                this._actionCallback.delete(reqId)
            })
    }

    _actionCheckRemove(event: CityBuildingCheckRemove) {
        const reqId = uuidv4()
        const param = BuildingCheckRemoveRequest.create({
            mapId: 1,
            buid: event.buildingItem.buildingUniqueId,
            reqId: reqId
        })
        this._actionCallback.set(reqId, (bs) => {
            const body = BuildingRemoveCheckReply.decode(bs);
            if (body.result != 0) lobby.instance.showToast(body.reason)
            else {
                let bs = this.userBuildings.get(1)!
                bs = bs.filter(b => b !== event.buildingItem)
                this.userBuildings.set(1, bs)
                const bc = configsHelper.loadBuildingConfig(event.buildingItem.buildingId)
                if (bc) {
                    const removeRewards = cmm.array2Map(bc.removeRewards, (it) => {
                        return {
                            k: it.propId.toNumber(),
                            v: it.num.toNumber()
                        }
                    })
                    lobby.instance.showAssetGainByMapToast(removeRewards)
                    const gainAssets: { [key: number]: Long } = {}
                    for (let reward of bc.removeRewards) {
                        gainAssets[reward.propId.toNumber()] = reward.num
                    }
                    const pc = propsConfigsLoader.findPropConfigs(configsHelper.loadBuildingConfig(event.buildingItem.buildingId)!.propId.toNumber())
                    lobby.instance.showToast(`开始拆除「${pc!.name}」！`)
                    director.emit(GameEvent.assetCoreChange, new AssetCoreChange(gainAssets));
                }
                director.emit(GameEvent.uiUpdateCity, event.buildingItem.buildingUniqueId)
                director.emit(GameEvent.cityBuildingRemoveComplete, event.buildingItem)
            }
        })
        http.requestAsync('POST', 'v1/building/check/remove', BuildingCheckRemoveResult, BuildingCheckRemoveRequest.encode(param).finish())
            .catch(err => {
                console.log(err)
                this._actionCallback.delete(reqId)
            })
    }

    _actionMove(event: CityBuildingPrePut) {
        const reqId = uuidv4()
        const param = BuildingMoveRequest.create({
            mapId: 1,
            buid: event.building.buildingItem!.buildingUniqueId,
            targetX: event.building!.tiledX,
            targetY: event.building!.tiledY,
            reqId: reqId
        })
        this._actionCallback.set(reqId, (bs) => {
            const body = BuildingMoveReply.decode(bs);
            if (body.result != 0) lobby.instance.showToast(body.reason)
            else {
                const bs = this.userBuildings.get(1)!
                const idx = bs.indexOf(event.building.buildingItem!)
                if (idx == -1) return
                const b = bs[idx]
                b.x = event.building!.tiledX
                b.y = event.building!.tiledY
                director.emit(GameEvent.cityBuildingMovePut, new CityBuildingMovePut(b))
                director.emit(GameEvent.uiUpdateCity, b.buildingUniqueId)
            }
        })
        http.requestAsync('POST', 'v1/building/move', BuildingMoveResult, BuildingMoveRequest.encode(param).finish())
            .catch(err => {
                console.log(err)
                this._actionCallback.delete(reqId)
            })
    }
}

